﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation.  All rights reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gadgeteer
{
    using Microsoft.SPOT.Hardware;
    using System;
    using System.Collections;
    using Gadgeteer.Modules;
    using SI = Gadgeteer.SocketInterfaces;

    /// <summary>
    /// A class representing a socket, which may be on a mainboard or on an expansion module such as an SPI multiplexer.  
    /// </summary>
    /// <remarks>
    /// This class is normally not directly used by application programs, who refer to sockets by their socket number. 
    /// Modules should normally use this class and the GT.Interfaces classes to access functionality required to implement their APIs.
    /// Mainboards and multiplexer modules providing sockets should use this class's SocketInterfaces subclass to declare functionalities provided on their sockets.
    /// </remarks>
    public partial class Socket
    {
        /// <summary>
        /// The <see cref="SI.AnalogInput" /> provider for this socket.
        /// </summary>
        public SI.AnalogInputIndirector AnalogInputIndirector;
        /// <summary>
        /// The <see cref="SI.AnalogOutput" /> provider for this socket.
        /// </summary>
        public SI.AnalogOutputIndirector AnalogOutputIndirector;
        /// <summary>
        /// The <see cref="SI.DigitalInput" /> provider for this socket.
        /// </summary>
        public SI.DigitalInputIndirector DigitalInputIndirector;
        /// <summary>
        /// The <see cref="SI.DigitalIO" /> provider for this socket.
        /// </summary>
        public SI.DigitalOIndirector DigitalIOIndirector;
        /// <summary>
        /// The <see cref="SI.DigitalOutput" /> provider for this socket.
        /// </summary>
        public SI.DigitalOutputIndirector DigitalOutputIndirector;
        /// <summary>
        /// The <see cref="SI.I2CBus" /> provider for this socket.
        /// </summary>
        public SI.I2CBusIndirector I2CBusIndirector;
        /// <summary>
        /// The <see cref="SI.InterruptInput" /> provider for this socket.
        /// </summary>
        public SI.InterruptInputIndirector InterruptIndirector;
        /// <summary>
        /// The <see cref="SI.PwmOutput" /> provider for this socket.
        /// </summary>
        public SI.PwmOutputIndirector PwmOutputIndirector;
        /// <summary>
        /// The <see cref="SI.Serial" /> provider for this socket.
        /// </summary>
        public SI.SerialIndirector SerialIndirector;
        /// <summary>
        /// The <see cref="SI.Spi" /> provider for this socket.
        /// </summary>
        public SI.SpiIndirector SpiIndirector;

        /// <summary>
        /// The socket number corresponding to this socket.  On mainboards, this is a positive number and is printed on the board itself. 
        /// For module-provided sockets (i.e. sockets you plug other modules into) this is an automatically generated negative number.
        /// </summary>
        public int SocketNumber { get; private set; }

        /// <summary>
        /// The name of the socket.  This is shown to users in any socket-related error messages generated by Gadgeteer Core.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Array of pins used by the socket.  This is always of size 11, with index [1] to [10] being the relevant Cpu.Pin for the Socket.Pin.
        /// Index 0 is unused.
        /// </summary>
        public Cpu.Pin[] CpuPins { get; private set; }

        /// <summary>
        /// The supported types of this socket.  
        /// </summary>
        public char[] SupportedTypes
        {
            get
            {
                return _SupportedTypes;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _SupportedTypes = value;
            }
        }
        private char[] _SupportedTypes = new char[] { };

        /// <summary>
        /// The SPI_module corresponding to this socket.  This is Socket.SPIMissing if there is no SPI module on this socket.
        /// </summary>
        public SPI.SPI_module SPIModule
        {
            get
            {
                return _SPIModule;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _SPIModule = value;
            }
        }
        private SPI.SPI_module _SPIModule = Socket.SocketInterfaces.SPIMissing;

        /// <summary>
        /// Returns the serial port name (e.g. "COM1") associated with a particular socket. 
        /// </summary>
        /// <returns>The serial port name</returns>
        public string SerialPortName
        {
            get
            {
                return _serialPortName;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _serialPortName = value;
            }
        }
        private string _serialPortName = null;

        /// <summary>
        /// Provides access to pulse width modulation (PWM) functionality on a socket pin 7. 
        /// </summary>
        /// <returns>An instance of the Cpu.PWMChannel enumeration, which should be PWM_NONE if there is no PWM support.</returns>
        public Cpu.PWMChannel PWM7
        {
            get
            {
                return _PWM7;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _PWM7 = value;
            }
        }
        private Cpu.PWMChannel _PWM7 = Cpu.PWMChannel.PWM_NONE;

        /// <summary>
        /// Provides access to pulse width modulation (PWM) functionality on a socket pin 8. 
        /// </summary>
        /// <returns>An instance of the Cpu.PWMChannel enumeration, which should be PWM_NONE if there is no PWM support on this socket pin.</returns>
        public Cpu.PWMChannel PWM8
        {
            get
            {
                return _PWM8;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _PWM8 = value;
            }
        }
        private Cpu.PWMChannel _PWM8 = Cpu.PWMChannel.PWM_NONE;

        /// <summary>
        /// Provides access to pulse width modulation (PWM) functionality on a socket pin 9. 
        /// </summary>
        /// <returns>An instance of the Cpu.PWMChannel enumeration, which should be PWM_NONE if there is no PWM support on this socket pin.</returns>
        public Cpu.PWMChannel PWM9
        {
            get
            {
                return _PWM9;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _PWM9 = value;
            }
        }
        private Cpu.PWMChannel _PWM9 = Cpu.PWMChannel.PWM_NONE;

        internal double AnalogInputScale = double.MinValue;
        internal double AnalogInputOffset = double.MinValue;
        internal int AnalogInputPrecisionInBits = int.MinValue;

        internal double AnalogOutputScale = double.MinValue;
        internal double AnalogOutputOffset = double.MinValue;
        internal int AnalogOutputPrecisionInBits = int.MinValue;

        /// <summary>
        /// Provides access to analog input functionality on a socket's pin 3.
        /// </summary>
        /// <returns>An instance of the Cpu.AnalogChannel enumeration, which should be ANALOG_NONE if there is no Analog input support on this socket pin.</returns>
        public Cpu.AnalogChannel AnalogInput3
        {
            get
            {
                return _AnalogInput3;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _AnalogInput3 = value;
            }
        }
        private Cpu.AnalogChannel _AnalogInput3 = Cpu.AnalogChannel.ANALOG_NONE;

        /// <summary>
        /// Provides access to analog input functionality on a socket's pin 4.
        /// </summary>
        /// <returns>An instance of the Cpu.AnalogChannel enumeration, which should be ANALOG_NONE if there is no Analog input support on this socket pin.</returns>
        public Cpu.AnalogChannel AnalogInput4
        {
            get
            {
                return _AnalogInput4;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _AnalogInput4 = value;
            }
        }
        private Cpu.AnalogChannel _AnalogInput4 = Cpu.AnalogChannel.ANALOG_NONE;

        /// <summary>
        /// Provides access to analog input functionality on a socket's pin 5.
        /// </summary>
        /// <returns>An instance of the Cpu.AnalogChannel enumeration, which should be ANALOG_NONE if there is no Analog input support on this socket pin.</returns>
        public Cpu.AnalogChannel AnalogInput5
        {
            get
            {
                return _AnalogInput5;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _AnalogInput5 = value;
            }
        }
        private Cpu.AnalogChannel _AnalogInput5 = Cpu.AnalogChannel.ANALOG_NONE;

        /// <summary>
        /// Provides access to analog output functionality on a socket's pin 5.
        /// </summary>
        /// <returns>An instance of the Cpu.AnalogChannel enumeration, which should be ANALOG_NONE if there is no Analog input support on this socket pin.</returns>
        public Cpu.AnalogOutputChannel AnalogOutput5
        {
            get
            {
                return _AnalogOutput5;
            }
            set
            {
                if (_registered) throw new SocketImmutableAfterRegistrationException();
                _AnalogOutput5 = value;
            }
        }
        private Cpu.AnalogOutputChannel _AnalogOutput5 = Cpu.AnalogOutputChannel.ANALOG_OUTPUT_NONE;
       
        internal static ArrayList _sockets = new ArrayList();

        /// <summary>
        /// A special socket number indicating that a module socket is not used.
        /// </summary>
        public static int Unused { get { return int.MinValue; } }


        /// <summary>
        /// Get the <see cref="Socket"/> corresponding to a socket number.
        /// </summary>
        /// <param name="socketNumber">The socket number</param>
        /// <param name="throwExceptionIfSocketNumberInvalid">Whether to throw an <see cref="InvalidSocketException"/> if the socket does not exist.</param>
        /// <param name="module">The module using this socket.</param>
        /// <param name="socketLabel">The label on the socket, if there is more than one socket on the module (can be null).</param>
        /// <returns>The socket corresponding to the provided socket number.</returns>
        public static Socket GetSocket(int socketNumber, bool throwExceptionIfSocketNumberInvalid, Module module, string socketLabel)
        {
            if (socketLabel == "") socketLabel = null;

            if (socketNumber == Socket.Unused)
            {
                if (throwExceptionIfSocketNumberInvalid)
                {
                    if (module == null)
                    {
                        throw new InvalidSocketException("Cannot get Socket for socket number Socket.NotConnected");
                    }
                    else
                    {
                        String errormessage = "Module " + module;
                        if (socketLabel != null) errormessage += " socket " + socketLabel;
                        errormessage += " must have a valid socket number specified (it does not support Socket.Unused)";
                        throw new InvalidSocketException(errormessage);
                    }
                }
                else
                {
                    return null;
                }
            }

            lock (_sockets)
            {
                for (int i = 0; i < _sockets.Count; i++)
                {
                    Socket socket = (Socket)_sockets[i];
                    if (socket.SocketNumber == socketNumber) return socket;
                }
            }
            if (throwExceptionIfSocketNumberInvalid)
            {
                if (module == null)
                {
                    throw new InvalidSocketException("Invalid socket number " + socketNumber + " specified.");
                }
                else
                {
                    String errormessage = "Module " + module;
                    if (socketLabel != null) errormessage += " socket " + socketLabel;
                    errormessage += " cannot be used with invalid socket number " + socketNumber;
                    throw new InvalidSocketException(errormessage);
                }
            }
            return null;
        }

        internal bool _registered = false;

        internal Socket(int socketNumber, string name)
        {
            this.Name = name;
            this.SocketNumber = socketNumber;
            this.CpuPins = new Cpu.Pin[11] { Socket.UnspecifiedPin, Socket.UnspecifiedPin, Socket.UnspecifiedPin, Socket.UnspecifiedPin, Socket.UnspecifiedPin, Socket.UnspecifiedPin, Socket.UnspecifiedPin, Socket.UnspecifiedPin, Socket.UnspecifiedPin, Socket.UnspecifiedPin, Socket.UnspecifiedPin };
        }

        /// <summary>
        /// Returns the Name of this socket.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Name;
        }


        /// <summary>
        /// Determines whether the specified socket supports the given socket type
        /// </summary>
        /// <param name="type">The socket type</param>
        /// <returns></returns>
        public bool SupportsType(char type)
        {
            return Array.IndexOf(_SupportedTypes, type) >= 0;
        }

        /// <summary>
        /// Checks that a given socket type is supported, and throws an <see cref="InvalidSocketException"/> if not. Optionally specifies the module which requires this type, resulting in a better error message.
        /// </summary>
        /// <param name="type">The socket type required.</param>
        /// <param name="module">The module requiring this socket type (can be null).</param>
        public void EnsureTypeIsSupported(char type, Module module)
        {
            if (!SupportsType(type))
            {
                throw new InvalidSocketException("Socket " + Name + " does not support type '" + type + "'" + (module != null ? "  required by " + module + " module." : "."));
            }
        }

        /// <summary>
        /// Checks that one of a given set of socket types is supported, and throws an <see cref="InvalidSocketException"/> if not. Optionally specifies the module which requires this type, resulting in a better error message.
        /// </summary>
        /// <param name="types">The array of socket types required (any one of these is sufficient).</param>
        /// <param name="module">The module requiring this socket type (can be null).</param>
        public void EnsureTypeIsSupported(char[] types, Module module)
        {
            for (int i = 0; i < types.Length; i++)
            {
                if (SupportsType(types[i])) return;
            }
            throw new InvalidSocketException("Socket " + Name + " does not support one of the types '" + new String(types) + "'" + (module != null ? "  required by " + module + " module." : "."));
        }

        /// <summary>
        /// An enumeration of socket pins.
        /// </summary>
        public enum Pin
        {
            /// <summary>
            /// Not applicable
            /// </summary>
            None = 0,
            /// <summary>
            /// Socket pin 1
            /// </summary>
            One = 1,
            /// <summary>
            /// Socket pin 2
            /// </summary>
            Two = 2,
            /// <summary>
            /// Socket pin 3
            /// </summary>
            Three = 3,
            /// <summary>
            /// Socket pin 4
            /// </summary>
            Four = 4,
            /// <summary>
            /// Socket pin 5
            /// </summary>
            Five = 5,
            /// <summary>
            /// Socket pin 6
            /// </summary>
            Six = 6,
            /// <summary>
            /// Socket pin 7
            /// </summary>
            Seven = 7,
            /// <summary>
            /// Socket pin 8
            /// </summary>
            Eight = 8,
            /// <summary>
            /// Socket pin 9
            /// </summary>
            Nine = 9,
            /// <summary>
            /// Socket pin 10
            /// </summary>
            Ten = 10
        }

        /// <summary>
        /// Tells GadgeteerCore that a pin is being used on this socket.
        /// This is called by Gadgteeer.Interface classes automatically. Gadgeteer.Modules which do not use a Gadgeteer.Interface helper class in using a pin should call this directly.
        /// </summary>
        /// <param name="pin">The socket pin being used</param>
        /// <param name="module">The module using the socket pin (can be null, but if it is not null a more useful error message will be generated).</param>
        /// <returns></returns>
        public Cpu.Pin ReservePin(Socket.Pin pin, Module module)
        {
            if (pin == Pin.None)
            {
                return UnspecifiedPin;
            }

            Cpu.Pin cpuPin = CpuPins[(int)pin];
            if (cpuPin == UnspecifiedPin)
            {
                throw new PinMissingException(this, pin);
            }

            if (cpuPin == UnnumberedPin)
            {
                // bypass checks, return no pin
                return Cpu.Pin.GPIO_NONE;
            }

            // see if this is a display socket and reboot if we need to disable the LCD controller
            if (!(module is Module.DisplayModule) && (SupportsType('R') || SupportsType('G') || SupportsType('B')))
            {
                Program.Mainboard.EnsureRgbSocketPinsAvailable();
            }

            return cpuPin;
        }

        /// <summary>
        /// An exception raised when a socket pin which is unspecified by the socket provider is used.
        /// </summary>
        public class PinMissingException : ApplicationException
        {
            internal PinMissingException(Socket socket, Socket.Pin pin)
                : base("\nPin " + (int)pin + " on socket " + socket + " is not connected to a valid CPU pin.")
            { }
        }

        /// <summary>
        /// An exception raised when an invalid socket is specified, e.g. a socket incompatible with the functionality required. 
        /// </summary>
        public class InvalidSocketException : ArgumentException
        {
            /// <summary>
            /// Generates a new invalid socket exception
            /// </summary>
            /// <param name="message">The exception cause</param>
            public InvalidSocketException(String message)
                : base(message)
            {
            }

            /// <summary>
            /// Throws an <see cref="InvalidSocketException" /> if a pin number is not in the specified range.
            /// </summary>
            /// <param name="pin">The pin number to test.</param>
            /// <param name="from">The lowest valid pin number.</param>
            /// <param name="to">The highest valid pin number.</param>
            /// <param name="iface">The requesting interface.</param>
            /// <param name="module">The requesting module.</param>
            /// <exception cref="InvalidSocketException">The <paramref name="pin" /> is out of the range specified by <paramref name="from" /> and <paramref name="to" />.</exception>
            /// <remarks>
            /// This method helps lowering the footprint and should be called when implementing a socket interface.
            /// </remarks>
            [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
            public static void ThrowIfOutOfRange(Socket.Pin pin, Socket.Pin from, Socket.Pin to, string iface, Module module)
            {
                if (pin >= from && pin <= to)
                    return;

                string message = "Cannot use " + iface + " interface on pin " + pin + " - pin must be in range " + from + " to " + to + ".";
                if (module != null)
                    message = "Module " + module + ": ";

                throw new InvalidSocketException(message);
            }

            /// <summary>
            /// Returns an <see cref="InvalidSocketException" /> with functionality error message.
            /// </summary>
            /// <param name="socket">The socket that has the error.</param>
            /// <param name="iface">The interface that is causing the error.</param>
            /// <returns>An <see cref="InvalidSocketException" /> with functionality error message.</returns>
            /// <remarks>
            /// This method helps lowering the footprint and should be called when implementing a socket interface.
            /// </remarks>
            [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
            public static InvalidSocketException FunctionalityException(Socket socket, string iface)
            {
                return new InvalidSocketException("Socket " + socket + " has an error with its " + iface + " functionality. Please try a different socket.");
            }

            /// <summary>
            /// Generates a new invalid socket exception
            /// </summary>
            /// <param name="message">The exception cause</param>
            /// <param name="e">The underlying exception</param>
            public InvalidSocketException(String message, Exception e)
                : base(message, e)
            {
            }
        }

        /// <summary>
        /// This exception is thrown when a socket which is already registered with GadgeteerCore is then modified.
        /// </summary>
        public class SocketImmutableAfterRegistrationException : InvalidOperationException
        {
            internal SocketImmutableAfterRegistrationException()
                : base("Socket data is immutable after socket is registered.")
            { }
        }

        /// <summary>
        /// A CPU pin which has no number, and for which reservation does not need to be tracked.
        /// </summary>
        public static readonly Cpu.Pin UnnumberedPin = (Cpu.Pin)int.MinValue;

        /// <summary>
        /// An unspecified CPU pin (e.g. for a socket which does not use this pin).
        /// </summary>
        public static readonly Cpu.Pin UnspecifiedPin = Cpu.Pin.GPIO_NONE;
    }

}